Prisma Next 0.16, payload-identifier routing, and digest-verified baked EQL migration artefacts#763
Conversation
All @prisma-next/* deps move 0.14.0 -> 0.16.0 in lockstep. prismaContract now requires the target's createNamespace factory (0.15 stopped materialising a placeholder namespace) — added to both configs and the bundled skill. The PSL interpreter test moves to the CST symbol-table input. Contracts re-emitted: postgres-schema namespace kind, StorageColumnTypes maps, scalarList capability; storage hashes unchanged.
…ifier
Every EQL v3 payload carries its required i: {t, c} identifier, and
ZeroKMS commits the cell key to it — the authoritative routing source.
decode now reads it first (ctx.column kept as fallback for non-v3
documents), and decodeJson builds fully-routed envelopes from it instead
of throwing, unblocking relation include() and aggregate projections.
…est-verified Replaces runtime injection: the framework applies extension migrations from on-disk vendored copies without the descriptor, and the seed phase never refreshes an existing package dir, so injection's environment- dependent hashes orphaned every consumer copy on each EQL bump (PN-MIG-5002) and the recomputed hash laundered tampering. Each migration self-emit now embeds readVerifiedInstallSql() — refused unless sha256 matches @cipherstash/eql's releaseManifest.installSqlSha256. One content-addressed identity flows from git through npm to vendored copies and the DB ledger. CI guards: provenance pin (committed SQL digest == installed manifest), frozen published-migration hashes, and byte-parity between the example's vendored space and the shipped artefacts. The example's stale vendored space is regenerated (stale v2 dir removed). Also fixes the upgrade migration's stale describe() space hashes. Follow-ups tracked in #762.
🦋 Changeset detectedLatest commit: 45c8d07 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Warning Review limit reached
Next review available in: 49 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (18)
📝 WalkthroughWalkthroughThis PR upgrades Prisma Next dependencies and contracts, embeds digest-verified EQL v3 SQL in published migrations, adds migration parity and integrity checks, and changes EQL v3 decoding to derive routing from payload identifiers. ChangesPrisma Next 0.16 contract and configuration
Digest-verified EQL v3 migrations
EQL v3 payload routing
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/prisma-next/src/v3/codec-runtime-v3.ts`:
- Around line 291-312: Update decodeJson in
packages/prisma-next/src/v3/codec-runtime-v3.ts to return null immediately when
the JSON value is null, matching decode’s nullable-column behavior before
routingKeyFromPayload runs. Add coverage in
packages/prisma-next/test/v3/codec-runtime-v3.test.ts at lines 351-361 verifying
decodeJson(null) round-trips as null; no other sites require changes.
In `@packages/prisma-next/test/psl-interpretation.test.ts`:
- Around line 17-20: Move packages/prisma-next/test/psl-interpretation.test.ts
into the appropriate __tests__/ directory and update its relative imports and
test configuration. Move
packages/prisma-next/test/v3/vendored-space-parity.test.ts under __tests__/ as
well, adjusting all relative paths for the new location while preserving both
suites’ behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7078b56a-8625-4946-8ae2-a8a50627609d
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (33)
.changeset/eql-baked-digest-verified.md.changeset/prisma-next-0-16.md.changeset/v3-payload-routing.mdexamples/prisma/migrations/cipherstash/20260601T0000_install_eql_bundle/migration.jsonexamples/prisma/migrations/cipherstash/20260601T0000_install_eql_bundle/ops.jsonexamples/prisma/migrations/cipherstash/20260601T0100_install_eql_v3_bundle/migration.jsonexamples/prisma/migrations/cipherstash/20260601T0100_install_eql_v3_bundle/ops.jsonexamples/prisma/migrations/cipherstash/20260720T0000_upgrade_eql_v3_3_0_2/migration.jsonexamples/prisma/migrations/cipherstash/contract.jsonexamples/prisma/migrations/cipherstash/refs/head.jsonexamples/prisma/package.jsonexamples/prisma/prisma-next.config.tsexamples/prisma/src/prisma/contract.d.tsexamples/prisma/src/prisma/contract.jsonpackages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/migration.jsonpackages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/migration.tspackages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/ops.jsonpackages/prisma-next/migrations/20260720T0000_upgrade_eql_v3_3_0_2/migration.jsonpackages/prisma-next/migrations/20260720T0000_upgrade_eql_v3_3_0_2/migration.tspackages/prisma-next/migrations/20260720T0000_upgrade_eql_v3_3_0_2/ops.jsonpackages/prisma-next/package.jsonpackages/prisma-next/prisma-next.config.tspackages/prisma-next/src/contract.d.tspackages/prisma-next/src/contract.jsonpackages/prisma-next/src/exports/control.tspackages/prisma-next/src/migration/eql-bundle-v3.tspackages/prisma-next/src/v3/codec-runtime-v3.tspackages/prisma-next/test/bundling-isolation.test.tspackages/prisma-next/test/psl-interpretation.test.tspackages/prisma-next/test/v3/codec-runtime-v3.test.tspackages/prisma-next/test/v3/migration-v3.test.tspackages/prisma-next/test/v3/vendored-space-parity.test.tsskills/stash-prisma-next/SKILL.md
💤 Files with no reviewable changes (1)
- examples/prisma/migrations/cipherstash/20260601T0000_install_eql_bundle/migration.json
Addresses code-review findings on the baked-EQL work: - migration-v3.test.ts: the provenance pin compared each migration's baked SQL digest against the LIVE @cipherstash/eql manifest, so it self- destructed on the next EQL bump (frozen 3.0.2 bytes can't match a 3.0.3 manifest) — pushing a maintainer toward re-emitting the published baseline. Reworked to a PUBLISHED_MIGRATIONS table with per-migration FROZEN digests (identity + baked-SQL, tied to each migration's own release) plus one bump-safe lockstep check (the installed release must be baked by SOME published migration) and an on-disk completeness check (no unpinned or stale migration dirs). - descriptor.test.ts: the 'injects the runtime EQL install SQL' test still named and described the deleted sentinel-injection design and compared against a live readInstallSql() (also bump-unsafe). Rewritten to assert the op carries the baked bundle verbatim, without the live comparison; dropped the now-unused import. - DEVELOPING.md: refreshed the sections that still documented the sentinel/ runtime-injection design and instructed re-emitting published migrations (the exact action the new doctrine and frozen-hash guard forbid); added the second (upgrade) migration to the layout and invariants. Reviewed in this branch; follow-ups (#4/#6/#8 payload-routing edges, efficiency items) tracked separately.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/prisma-next/test/descriptor.test.ts`:
- Around line 120-129: The descriptor test’s loose substring and size checks do
not verify the exact migration bundle. Update the assertions around installOp
and its execute SQL to reuse the frozen SHA-256/digest verification established
by migration-v3.test.ts, comparing the embedded SQL against the expected release
provenance without calling readInstallSql() or reading the installed package at
runtime.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e96a0ffb-1adb-43ed-afdc-ae47ebe331c1
📒 Files selected for processing (3)
packages/prisma-next/DEVELOPING.mdpackages/prisma-next/test/descriptor.test.tspackages/prisma-next/test/v3/migration-v3.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/prisma-next/test/v3/migration-v3.test.ts
CodeRabbit flagged decodeJson(null) as throwing. It is unreachable: the SQL runtime null-guards before both codec methods — decodeField before decode, and sql-orm-client's include-decode loop (plus its many-typed element path) before decodeJson — so a NULL cell never reaches the codec. Null-handling is the runtime's contract; returning null here would also break the framework's decodeJson(json): TInput envelope signature. Comment only, no behavior change.
…t migrate'
Prisma Next has no 'migration apply' subcommand — the apply verb is the
top-level 'prisma-next migrate' ('migration' only groups plan/new/show/
status/log/list/graph/check). Running the prescribed 'migration apply'
errors: 'Unknown command: apply. Use prisma-next migrate --to <contract>'.
Fixes the stale name in the stash-prisma-next and stash-cli skills, the
@cipherstash/prisma-next README, and — user-visibly — stash init
--prisma-next's printed next-steps, the init flag help, and the stash eql
install Prisma-Next refusal message. Updated the CLI unit + e2e assertions
that pinned the old string. Historical CHANGELOG entries left as-is.
Surfaced by the rc.4 skilltester run (found at PN 0.14.0, confirmed 0.16.0);
tracked with the other skilltester findings in a follow-up issue.
freshtonic
left a comment
There was a problem hiding this comment.
Reviewed by Claude Code on behalf of James Sadler.
Verdict: Approve
I reviewed all three bundled concerns with a focus on the digest verification, payload-identifier routing, and the dependency upgrade. The security-sensitive code is correct and well-tested. No blocking issues.
Digest verification (eql-bundle-v3.ts) — correct
assertInstallSqlDigestcomputessha256over the install SQL and compares againstreleaseManifest.installSqlSha256, throwing on mismatch (fails closed). Right algorithm, right bytes, right direction.readVerifiedInstallSql()is the sole sanctioned emit source, so anops.jsoncan never be baked from bytes the pinned release does not attest to. Consistency of the digest computation is proven byassertInstallSqlDigest(readInstallSql())passing in CI.- Apply-time integrity correctly hands off to the framework's existing
verifyMigrationHashover the on-disk bytes (the baked SQL is insideops.json, so tampering changes themigrationHash). The e2e tamper test (injectedDROP TABLErejected before execution) confirms the trust chain end-to-end. - The provenance test suite is well-designed: per-migration FROZEN literals (
migrationHash+installSqlSha256) tied to each migration's OWN release, a completeness check against the on-disk directory set, and a single bump-safe LOCKSTEP check (.toContain(releaseManifest.installSqlSha256)). An EQL bump without a matching re-emit fails, while historical migrations keep their frozen digests. The committedmigration.jsonhashes (2c873907…,7bb96043…) match the frozen literals.
Payload-identifier routing (codec-runtime-v3.ts) — cryptographically sound
- Reading
(t, c)from the payload's owniidentifier is authoritative because ZeroKMS commits the cell key to that identifier: a relocated/tampered payload fails to decrypt (fails closed). Trustingiis therefore no weaker than trusting the query AST, and it unblocks aggregates, computed projections, anddecodeJson(relation includes). - No cross-tenant risk: the codec instance closes over a fixed
this.#sdk(which carries tenant/dataset credentials). The payload only influences(table, column), never the SDK, so a payload cannot redirect decryption to another tenant. routingKeyFromPayloadcorrectly rejects non-object/null/empty-string cases.decodekeepsctx.columnas a fallback only when no identifier is present;decodeJsonfails closed with a diagnostic. Good test coverage including the prefer-payload-over-ctx case and the non-round-trippableencodeJsonmarker.
Prisma Next 0.16 upgrade — coherent
- 31
@prisma-next/*pins bumped in lockstep;pnpm-lock.yamlshows only registry (npmjs integrity) resolutions — no git/tarball sources. createNamespace: postgresCreateNamespaceadded to both configs (0.15 requirement) and the PSL interpreter test migrated to the CSTparse+buildSymbolTableinput, consistent with the changeset notes.
Non-blocking observations
- Baseline bytes changed (one-time invariant exception). This PR changes the baseline migration's
ops.json/hash (35fc9000…→2c873907…) while simultaneously establishing the "published migrations are immutable by dirName, never re-emit" invariant. That is a legitimate transition commit (moving off the sentinel/runtime-injection scheme) and the PR body documents the rc-consumer reset, but it is worth being explicit that this is a deliberate one-time exception to the very invariant being introduced — after this lands, the append-only rule must hold strictly. decodenow silently prefers the payload identifier even when it disagrees withctx.column. Intentional and safe (key commitment makes it the correct identity), but it removes the implicit cross-check that previously surfaced misplaced-data bugs as decryption failures. A value that ends up in the wrong column now decodes silently by its true identity. Not a security issue, but a low-cost diagnostic (warn whenroutingKeyFromPayloadandctx.columndisagree) would preserve that signal. Optional.- Both migrations bake identical 3.0.2 SQL (same
installSqlSha256); baseline+upgrade both land on the 3.0.2 surface for a fresh DB, relying on the install bundle's re-install idempotency. Fine and documented, just noting it is load-bearing on that idempotency claim.
…dback) Addresses the reviewer's non-blocking note on #763: identifier-first v3 routing decrypts a value by its own `i:{t,c}` identity even when the projected column disagrees, which removes the implicit cross-check that column-first routing gave for free (a misplaced value used to fail to decrypt). This re-surfaces that signal. `decode` now compares the payload identifier against the projected column and, on disagreement, emits a `console.warn` naming both — schema identifiers only, never plaintext/ciphertext. Routing is UNCHANGED: the payload identifier stays authoritative (ZeroKMS commits the cell key to it). It warns rather than throws, and dedupes once per distinct mismatch (a per-instance Set), because the same disagreement is the expected benign shape of an un-re-encrypted column rename — so it can't spam the hot path or cry wolf on a supported workflow. Mirrors the once-per-process console.warn pattern already used in @cipherstash/stack. 3 new unit tests: warn-once-per-distinct-mismatch, a fresh warn for a different mismatch, and no-warn on agreement / no-context (aggregates). Also documents, in the baked-EQL changeset, that this PR's one-time baseline re-emit is a deliberate exception to the append-only invariant it introduces (reviewer note #1). prisma-next unit suite: 344 pass; build/dts clean; code:check clean.
|
Addressed the review feedback in @freshtonic (approved, non-blocking notes)#2 — warn when the payload identifier disagrees with Two refinements over a naive per-cell warn, worth flagging since they're deliberate:
Mirrors the once-per-process #1 — one-time baseline re-emit is an invariant exception. Documented explicitly in #3 — both migrations bake identical 3.0.2 SQL, load-bearing on re-install idempotency. Informational; no change (agreed, and it's documented). CodeRabbit (3 inline comments) — verified already handled, no change
prisma-next unit suite: 344 pass; build/dts clean; |
Three related pieces of work on the Prisma Next integration, in review order per commit:
1. Upgrade to Prisma Next 0.16 (from 0.14)
All 31
@prisma-next/*pins move to 0.16.0 in lockstep. The CipherStash encryption surface is unchanged. Notable:createNamespaceis now required inprismaContract(...)(0.15 stopped materialising a placeholder namespace) — added to both configs and documented in the bundledstash-prisma-nextskill.parse+buildSymbolTable).postgres-schemanamespace kind,StorageColumnTypesmaps,scalarListcapability, discrete FK/index entities. Storage hashes unchanged.Verified: unit + live-PG (27) + integration (595) suites green with a
~/.cipherstashdevice profile and the local compose Postgres.2. Route v3 decoding from the EQL payload's own identifier
Every EQL v3 payload carries its required
i: {"t", "c"}identifier, and ZeroKMS commits the cell key to it — making it the authoritative routing source, not the query AST.decodenow reads it first (projected-column context kept as fallback for non-v3 documents), anddecodeJsonbuilds fully-routed envelopes from it instead of throwing.This unblocks relation
include()(the SQL ORM decodes included cells throughdecodeJsonwith no column ref available) and aggregate/computed projections. A two-modelinclude()e2e test remains a follow-up (needs thedb-ref planning workflow — see #762).3. Bake the EQL install SQL into migration artefacts, digest-verified
Replaces the runtime-injection design. The framework applies extension migrations from on-disk vendored copies without the descriptor, and the CLI seed phase never refreshes an existing package directory — so injection's environment-dependent hashes orphaned every consumer's vendored copy on each EQL bump (
PN-MIG-5002), and recomputing the hash from installed content meant tampering could never be detected.Now each migration's self-emit embeds
readVerifiedInstallSql()— refused unless its sha256 matches@cipherstash/eql'sreleaseManifest.installSqlSha256. One content-addressed identity flows from this repo's git history through the npm tarball into consumers' vendored copies and the DB ledger. EQL upgrades ship as new append-only migration directories (the existing invariant-edge pattern).CI guards added:
migrations/cipherstash/must be byte-identical to the shipped artefacts (catches the exact staleness class that wedged it).Proven end-to-end: seed phase materialises byte-identical copies; fresh-DB
migrateinstalls EQL 3.0.2 and records both invariants; a tampered vendoredops.json(injectedDROP TABLE) is rejected by hash verification before execution; example e2e (40), live-PG (27), and unit (341) suites green.dist/control.jsgrows to ~5 MB (inlined artefacts, control-plane entry only — runtime entries unchanged, pinned by the bundling-isolation test).Also fixes the upgrade migration's stale
describe()space hashes and theeql-3.0.0→3.0.2label drift.rc consumers (none external today): delete
migrations/cipherstash/and re-runprisma-next migration planonce — the seed phase regenerates it byte-identical.Follow-ups tracked in #762.
Summary by CodeRabbit
scalarListand expanded generated storage type mappings.createNamespacesetup and post-upgrade regeneration expectations; clarifies changes when upgrading EQL v3 and migration publication behavior.Closes #765.